﻿using System.Collections.Generic;
using System.Threading;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using DeepEarth.Toolkit.Common;
using System.Collections.ObjectModel;
using DeepEarth.Toolkit.Geometry.Data;
using DeepEarth.Map.Core;
using System.Windows;
using System;

namespace DeepEarth.Toolkit.Controls
{
    [TemplatePart(Name = PART_pointPutton, Type = typeof(ToggleButton))]
    [TemplatePart(Name = PART_freeDrawButton, Type = typeof(ToggleButton))]
    [TemplatePart(Name = PART_lineButton, Type = typeof(ToggleButton))]
    [TemplatePart(Name = PART_polygonButton, Type = typeof(ToggleButton))]
    public class DrawingTool : MapInteractionControl, IDisposable
    {
        private const string PART_pointPutton       = "PART_pointButton";
        private const string PART_freeDrawButton    = "PART_freeDrawButton";
        private const string PART_lineButton        = "PART_lineButton";
        private const string PART_polygonButton     = "PART_polygonButton";
        
        private readonly IDictionary<ToggleButton, IDisposable> _subscriptions;

    	private readonly ObservableCollection<Object> _mapItems;

        private ToggleButton _pointButton;
        private ToggleButton _freeDrawButton;
        private ToggleButton _lineButton;
        private ToggleButton _polygonButton;

        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation", typeof(Orientation), typeof(DrawingTool), new PropertyMetadata(Orientation.Horizontal));

        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        /// <summary>
        /// Geometry converters are used to convert the "base" geometry user draw on the map 
        /// into the "real" business objects.
        /// </summary>
        /// <remarks>
        /// <para>
		///  <see cref="IValueConverter"/> instances convert "base" geometry types used draw on the map into
        /// the "real" business model/view objects.
        /// </para>
        /// <para>
        /// When user draws shapes or adds pushpins, it really means something in terms of the business logic of the application.
        /// For example, on the map displaying bicycle stations each pushpin user adds probably means "a new station is here".
        /// So it make sense to have (and display on the map) a "BicycleStation" object, which may have its own template, etc, 
        /// rather than "standard" Geometry.Pushpin.
        /// </para>
        /// <para>
        /// In Runtime user can event switch between different kind of objects (BicycleStation, PetrolStation, MyBicycleWasStolenHere, etc).
        /// Technically it can be done by changing the concrete geometry converter.
        /// </para>
        /// </remarks>
        public IValueConverter GeometryConverter
        {
			get { return (IValueConverter)GetValue(GeometryConverterProperty); }
            set { SetValue(GeometryConverterProperty, value); }
        }

        public static readonly DependencyProperty GeometryConverterProperty =
            DependencyProperty.Register("GeometryConverter",
										typeof(IValueConverter),
                                        typeof (DrawingTool),
                                        new PropertyMetadata(new DefaultDrawnGeometryConverter()));


        public DrawingTool()
        {
            _subscriptions  = new Dictionary<ToggleButton, IDisposable>();
			_mapItems       = new ObservableCollection<Object>();
            DefaultStyleKey = typeof(DrawingTool);
        }

		protected override void GeoItemsChanged(Core.Controls.GeoItems newMap, Core.Controls.GeoItems oldMap)
		{
			if (oldMap != null)
				oldMap.Remove(_mapItems);

			if (newMap != null)
				newMap.Add(_mapItems);
		}

        protected override void MapChanged(IMap newMap, IMap oldMap)
        {
            UnsubscribeAllDrawings();
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            //find buttons in the template and subscribe for their events.

            _pointButton    = (ToggleButton) GetTemplateChild(PART_pointPutton);
            if (_pointButton != null)
                _pointButton.Click += (s, e) => SwitchToPushpin(_pointButton);

            _freeDrawButton = (ToggleButton) GetTemplateChild(PART_freeDrawButton);
            if (_freeDrawButton != null)
                _freeDrawButton.Click += (s, e) => SwitchToShape(_freeDrawButton, 0, false);

            _lineButton = (ToggleButton) GetTemplateChild(PART_lineButton);
            if (_lineButton != null)
                _lineButton.Click += (s, e) => SwitchToShape(_lineButton, 2, false);

            _polygonButton = (ToggleButton) GetTemplateChild(PART_polygonButton);
            if (_polygonButton != null)
                _polygonButton.Click += (s, e) => SwitchToShape(_polygonButton, 0, true);

            IsEnabled = true;
        }

        /// <summary>
        /// Switches the drawing tool to the specific drawing mode
        /// </summary>
        /// <param name="button">The button causing the event.</param>
        /// <param name="maxPoints">Maximum points user can draw.</param>
        /// <param name="closePolygon">Force closing a polygon user draws.</param>
        private void SwitchToShape(ToggleButton button, int maxPoints, bool closePolygon)
        {
            //reset all the previous drawing modes
            UnsubscribeAllDrawings();

            //only if button is "checked", set the appropriate mode.
            if (IsToggleButtonChecked(button))
            {
                //create the drawing observable and subscribe for the results
                var subscription = DrawingObservable.CreateShape(this, maxPoints, closePolygon)
                    .Subscribe(AddDrawnObject);

                //Save the subscription to be able to dispose it when we don't need it anymore.
                _subscriptions[button] = subscription;
            }
        }

        /// <summary>
        /// Activates the "Pushpin" drawing mode (user just adds pushpins by clicking on the map).
        /// </summary>
        /// <param name="button">The button causing the event.</param>
        private void SwitchToPushpin(ToggleButton button)
        {
            //reset all the previous drawing modes
            UnsubscribeAllDrawings();

            //only if button is "checked", set the appropriate mode.
            if (IsToggleButtonChecked(button))
            {
                //create the drawing observable and subscribe for the results
                var subscription = DrawingObservable.Create(this, DrawingType.Pushpin)
                    .Subscribe(AddDrawnObject);

                //Save the subscription to be able to dispose it when we don't need it anymore.
                _subscriptions[button] = subscription;
            }
        }

        /// <summary>
        /// Unsubscribes and resets all the drawing modes.
        /// </summary>
        private void UnsubscribeAllDrawings()
        {
            foreach (var subscription in _subscriptions)
            {
                //"uncheck" the button
                subscription.Key.IsChecked = false;

                //dispose the subscription
                if (subscription.Value != null) subscription.Value.Dispose();
            }

            _subscriptions.Clear();
        }

        /// <summary>
        /// Adds the "result" drawn object to the collection
        /// </summary>
        /// <param name="geometry">A "raw" drawn object ("basic" geometry).</param>
        private void AddDrawnObject(Geometry.Data.Geometry geometry)
        {
            //Try to convert the "basic" shape into the "real" business object
            var objectToAdd = (GeometryConverter != null)
                ? GeometryConverter.Convert(geometry, typeof(object), null, Thread.CurrentThread.CurrentCulture)
                : geometry;

            //Add the object to the result collection.
            _mapItems.Add(objectToAdd);
        }

        #region Add Complete Geometry 


        private void AddLabel()
        {
            var label = new LabelGeometry
            {
                Position = View.Center,
                Text = "Edit me",
            };

            AddDrawnObject(label);
        }

        #endregion

        private static bool IsToggleButtonChecked(ToggleButton button)
        {
            return (button != null && button.IsChecked.GetValueOrDefault(false));
        }

        public void Dispose()
        {
            UnsubscribeAllDrawings();
        }
    }
}
